﻿using System;
using System.Collections.Generic;
using System.Linq;
using TLang.Values;

namespace TLang.Ast
{
    public class DotNode : Node
    {
        public readonly List<Node> Elements;

        public DotNode(List<Node> elements, String file, int start, int end, int line, int col) : base(file, start, end, line, col)
        {
            this.Elements = elements;
        }

        public override Value Interp(Scope s)
        {
            Value firstValue = this.Elements[0].Interp(s);
            return Interp(firstValue, this.Elements[0], this.Elements.Skip(1).ToList(), s);
        }

        public Node Locate(Scope s)
        {
            Node firstNode = this.Elements[0];
            Value firstValue = firstNode.Interp(s);

            return Locate(firstNode, firstValue, this.Elements.Skip(1).ToList(), s);
        }

        private static Node Locate(Node firstNode, Value firstValue, List<Node> restNode, Scope s)
        {
            if (restNode.Count == 0)
            {
                return firstNode;
            }
            if (firstValue is VectorValue vector)
            {
                Node nextNode = restNode[0];
                Value nextValue = nextNode.Interp(s);
                if (!(nextValue is IntValue i))
                {
                    throw Util.GeneralError(nextNode.ToString() + " must be int");
                }
                firstNode = new SubscriptNode(firstNode, nextNode, firstNode.File, firstNode.Start, firstNode.End, firstNode.Line, firstNode.Col);
                firstValue = firstNode.Interp(s);
                return Locate(firstNode, firstValue, restNode.Skip(1).ToList(), s);
            }
            else if (firstValue is RecordType record)
            {
                Node nextNode = restNode[0];
                if (nextNode is NameNode nameNode)
                {
                    Value value = record.Properties.LookupLocal(nameNode.Id);
                    if (value != null)
                    {
                        firstNode = new SubscriptNode(firstNode, restNode[0], firstNode.File, firstNode.Start, firstNode.End, firstNode.Line, firstNode.Col);
                        return Locate(firstNode, value, restNode.Skip(1).ToList(), s);
                    }
                }
                Value nextValue = nextNode.Interp(s);
                if (!(nextValue is StringValue v))
                {
                    throw Util.GeneralError(restNode[0].ToString() + " must be string");
                }

                firstNode = new SubscriptNode(firstNode, restNode[0], firstNode.File, firstNode.Start, firstNode.End, firstNode.Line, firstNode.Col);
                firstValue = record.Properties.Lookup(v.Value);
                return Locate(firstNode, firstValue, restNode.Skip(1).ToList(), s);
            }
            throw Util.GeneralError(restNode[0].ToString() + " must be vector or record");
        }

        private Value Interp(Value firstValue, Node firstNode, List<Node> restNode, Scope s)
        {
            if (restNode.Count == 0)
            {
                return firstValue;
            }
            if (firstValue is VectorValue vector)
            {
                Node nextNode = restNode[0];
                Value nextValue = nextNode.Interp(s);
                if (!(nextValue is IntValue i))
                {
                    throw Util.GeneralError(nextNode.ToString() + " must be int");
                }
                firstValue = vector[i.Value];
                return Interp(firstValue, nextNode, restNode.Skip(1).ToList(), s);
            }
            else if (firstValue is RecordType record)
            {
                Node nextNode = restNode[0];
                if (nextNode is NameNode nameNode)
                {
                    Value value = record.Properties.LookupLocal(nameNode.Id);
                    if (value != null)
                    {
                        return Interp(value, restNode[0], restNode.Skip(1).ToList(), s);
                    }
                }
                Value nextValue = nextNode.Interp(s);
                if (!(nextValue is StringValue v))
                {
                    throw Util.GeneralError(restNode[0].ToString() + " must be string");
                }
                firstValue = record.Properties.LookupLocal(v.Value);
                return Interp(firstValue, restNode[0], restNode.Skip(1).ToList(), s);
            }
            else if (firstValue is StringValue str)
            {
                Node nextNode = restNode[0];
                Value nextValue = nextNode.Interp(s);
                if (!(nextValue is IntValue i))
                {
                    throw Util.GeneralError(nextNode.ToString() + " must be int");
                }
                return new CharValue(str.Value[i.Value]);
            }
            throw Util.GeneralError(firstNode.ToString() + " must be vector or record");
        }

        public override string ToString()
        {
            return string.Join(".", Elements?.Select(a => a.ToString()));
        }
    }
}
